#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <limits.h>
#include <sys/param.h>

#include "main.h"
#include "request.h"
#include "xmalloc.h"
#include "dict.h"
#include "utils.h"
#include "tag.h"
#include "var.h"
#include "url.h"
#include "static_file.h"
#include "session.h"
#include "cookie.h"

#ifdef ENABLE_PTHREADS
#include <pthread.h>
static pthread_mutex_t inc_request_num = PTHREAD_MUTEX_INITIALIZER;
#endif
static size_t request_num = 0;
static reqhndl_cb_t request_handler;


#define req_strndup(d, s, len) d = (char *) ncmalloc(len+1, sizeof(char)); strncpy(d, s, len); *(d + len) = '\0';
#define req_strfree(d, len) ncfree(d, len*sizeof(char)+1);

request_t *
request_init(int thrdnum, const char *qry, const char *method, const char *doc_uri, void *__unused, bool static_req, dict_t *http_hdr)
{
  request_t *req = ncmalloc(1, sizeof(request_t));

  req->thrd_num = thrdnum;
#ifdef ENABLE_PTHREADS
  pthread_mutex_lock(&inc_request_num);
#endif
  request_num++;
  req->req_num = request_num;
#ifdef ENABLE_PTHREADS
  pthread_mutex_unlock(&inc_request_num);
#endif
  req->static_req = static_req;
  req->__unused = __unused;
  req->http_hdr = http_hdr;

  if (!static_req) {
    req_strndup(req->qry, qry, QUERY_LEN);
    req_strndup(req->method, method, METHOD_LEN);
    req_strndup(req->doc_uri, doc_uri, DOC_LEN);
  } else {
    req->qry = (char *)qry;
    req->method = (char *)method;
    req->doc_uri = (char *)doc_uri;
  }

  return (req);
}

void
request_free(request_t *req)
{
  if (!req->static_req) {
    req_strfree(req->qry, QUERY_LEN);
    req_strfree(req->method, METHOD_LEN);
    req_strfree(req->doc_uri, DOC_LEN);
  }
  free_dict(req->http_hdr);
  ncfree(req, sizeof(request_t));
}

#if defined(ENABLE_DEBUG) && defined(ENABLE_REQDBG)
void
print_request_debug(request_t *fr)
{
  D("[REQUEST_DBG]\n"
    "  QUERY    : %s\n"
    "  METHOD   : %s\n"
    "  DOC_URI  : %s\n",
    fr->qry, fr->method, fr->doc_uri);
}
#endif

size_t
requests_processed(void)
{
  return (request_num);
}

void
set_request_handler(reqhndl_cb_t __request_handler_cb)
{
  request_handler = __request_handler_cb;
}

int
process_request(struct request *fr)
{
  bool sent = false;
  int ffd;
  size_t idx, idx2, rpos = 0;
  void *unused;
  parse_data_t *pd = NULL;
  tag_data_t *td = NULL;
  string_t *realpath;
  string_t *result = NULL;
  string_t *output = NULL;
  var_reqdata_t vr;

  string_t *url = url_parse_path(fr->doc_uri);
  url_object_t *uo = url_get(url);
  map_t qry = url_parse_query(fr->qry);
  ck_map_t scks, cks;
  dict_t *hdr = NULL;
  session_t *ses;

#ifdef ENABLE_REQDBG
  print_hashmap_debug("QUERY", qry);
  I("[%d][%lu] %s %s %s", fr->thrd_num, (long unsigned int)fr->req_num, fr->method, get_string(url), fr->qry);
#else
  I("Thread(%d): %s %s", fr->thrd_num, fr->method, get_string(url));
#endif
  if (uo) switch (uo->type) {
    case URL_BUILTIN:
    for (idx = 0; idx < pdata->used; idx++) {
      pd = (parse_data_t *) array_get_offset(pdata, idx);
      if ( strcmp(get_string(pd->file_name), get_string(uo->path)) == 0 ) {
        output = create_string(get_string(pd->html));
        result = create_empty_string();
        assert(output && result);
        /* parse cookies */
        cks = parse_cookies(fr->http_hdr);
        assert(cks);
        /* get session */
        ses = get_session_from_cookie(cks);
        assert(ses);
        if (pd->tags) {
          for (idx2 = 0; idx2 < pd->tags->used; idx2++) {
            td = (tag_data_t *) array_get_offset(pd->tags, idx2);
            if (td->var_ptr && td->var_ptr->vname && td->var_ptr->vcbs.request_cb) {
              set_string(result, "");
              vr.req = fr;
              vr.pd = pd;
              vr.result = result;
              vr.unused = &unused;
              vr.attrs = (dict_t *) td->attrs;
#ifdef ENABLE_REQDBG
              print_dict_debug(vr.attrs);
#endif
              vr.qry = qry;
              td->var_ptr->vcbs.request_cb(&vr);

              insert_string(output, td->idx + rpos, get_string(result), get_strlen(result));
              rpos += get_strlen(result);
              RD("[%d][%lu] VAR: %s | POS: %lu | RPOS: %lu", fr->thrd_num, (long unsigned int)fr->req_num, td->var_ptr->vname, (long unsigned int)td->idx, (long unsigned int)rpos);
            }
          }
        }

        /* create the http header */
        hdr = create_dict(true);
        insert_s(hdr, "Content-Type", "text/html; charset=utf-8");
        insert_f(hdr, "Content-Length", "%ld", get_strlen(output));
        /* create cookie's */
        scks = cookie_map_new();
        check_session(ses, scks);
#ifdef ENABLE_REQDBG
        DD2("cookies");
        cookie_debug(cks);
        DD2("sending cookies");
        cookie_debug(scks);
#endif
        /* call the request handler function */
        request_handler(fr, output, hdr, scks);
        sent = true;

        cookie_map_free(cks);
        cookie_map_free(scks);
        free_dict(hdr);
        free_string(result);
        free_string(output);
        break;
      }
    }
    /* builtin has no html file */
    if (!sent) {
      hdr = create_dict(true);
      output = create_string(NOTFOUND_TEXT);
      insert_f(hdr, "Content-Length", "%ld", get_strlen(output));
      request_handler(fr, output, hdr, NULL);
      free_string(output);
      free_dict(hdr);
    }
  break;
  case URL_STATIC:
    RD("[%d][%lu] serving static file %s", fr->thrd_num, (long unsigned int)fr->req_num, get_string(url));
    file_ext_t *xt = get_file(get_string(uo->path));
    if (xt) {
      RD("[%d][%lu] sending file %s", fr->thrd_num, (long unsigned int)fr->req_num, get_string(xt->delem->path));
      hdr = create_dict(true);
      insert_s(hdr, "Content-Type", xt->mime);
      insert_f(hdr, "Content-Length", "%ld", xt->delem->siz);
      request_handler(fr, xt->delem->data, hdr, NULL);
      sent = true;
      free_dict(hdr);
    } else {
      RD("[%d][%lu] invalid static file", fr->thrd_num, (long unsigned int)fr->req_num);
    }
  break;
  } else {
    RD("[%d][%lu] try to find %s on the filesystem ..", fr->thrd_num, (long unsigned int)fr->req_num, get_string(url));
    realpath = check_rpath(doc_dir, get_string(url));
    if (get_strlen(realpath) > 0) {
      hdr = create_dict(true);
      /* load file into memory */
      ffd = open_file(get_string(realpath));
      output = mmap_fd_str(ffd);
      if (!output) {
        switch (errno) {
          case ENOENT:
            output = create_string(NOTFOUND_TEXT);
            break;
          default:
            output = create_string(UNKNOWN_TEXT);
            break;
        }
      }
      /* create the http header */
      insert_f(hdr, "Content-Length", "%ld", get_strlen(output));
      /* call the request handler function */
      request_handler(fr, output, hdr, NULL);
      sent = true;
      free_string(output);
      free_dict(hdr);
    }
    free_string(realpath);
  }

  free_string(url);
  url_free_query(&qry);
  return (0);
}
